/*
 *  Copyright 2013 National Institute of Advanced Industrial Science and Technology
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.sss.tool;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.sss.client.DataGetter;
import org.sss.client.SssClient;
import org.sss.mapreduce.Cluster;
import org.sss.mapreduce.GroupID;
import org.sss.mapreduce.StorageNode;
import org.sss.mapreduce.Tuple;
import org.sss.mapreduce.datatype.PackableString;
import org.sss.mapreduce.datatype.TimedPackableLong;

public class LogCountLister {
  private boolean debug;
  private boolean time;
  private GroupID gid;
  private List<StorageNode> storageNodes;
  private final SssClient client;
  private LogCountLister(SssClient client) {
    this.debug  = false;
    this.time = false;
    this.storageNodes = null;
    this.client = client;
  }
  private void usage(final Options options) {
    HelpFormatter formatter = new HelpFormatter();
    formatter.printHelp("LogCountLister [options] gid", options);
    System.exit(-1);
  }
  private void parseArgs(String[] args) {
    Options options = new Options();
    options.addOption("d", false, "debug option");
    options.addOption("t", false, "display time option");
    OptionBuilder.withArgName("nodelist");
    OptionBuilder.hasArg();
    OptionBuilder.withDescription("use specified nodes only " +
                                  "(node index separated by comma)");
    Option node = OptionBuilder.create("n");
    options.addOption(node);
    CommandLineParser parser = new BasicParser();
    CommandLine commandLine = null;
    int[] nodeIndex = null;
    try {
      commandLine = parser.parse(options, args);
      if (commandLine.hasOption("d")) { debug  = true; }
      if (commandLine.hasOption("t")) { time = true; }
      if (commandLine.hasOption("n")) {
        String[] nodeList = commandLine.getOptionValues("n")[0].split(",");
        nodeIndex = new int[nodeList.length];
        int i = 0;
        for (String n: nodeList) {
          nodeIndex[i++] = Integer.parseInt(n);
        }
      }
      String[] gidList = commandLine.getArgs();
      if (gidList.length != 1) {
        System.err.println(gidList.length < 1 ?
                           "no GroupID specified." :
                           "too many GrpupIDs specified.");
        usage(options);
      }
      gid = GroupID.createFromString(gidList[0]);
    }
    catch (ParseException e) {
      System.out.println(e);
      usage(options);
    }
    catch (IllegalArgumentException e) {
      System.err.println("invalid GroupID string specified.");
      usage(options);
    }
    try {
      Cluster cluster;
      cluster = Cluster.loadDefaultConfigFile();
      this.storageNodes = new ArrayList<StorageNode>();
      if (nodeIndex != null) {
        for (int i=0; i<nodeIndex.length; i++) {
          this.storageNodes.add(cluster.getStorageNodes().get(nodeIndex[i]));
        }
      }
      else {
        this.storageNodes.addAll(cluster.getStorageNodes());
      }
    }
    catch (IndexOutOfBoundsException e) {
      System.err.println("specified node index is out of range.");
      System.exit(-1);
    }
    catch (Exception e) {
      System.err.println("cannot load cluster configuration file.");
      System.exit(-1);
    }
  }
  private <T> Iterator<T> sortedIterator(Iterator<T> it, Comparator<T> comparator) {
    List<T> list = new ArrayList<T>();
    while (it.hasNext()) {
      list.add(it.next());
    }
    Collections.sort(list, comparator);
    return list.iterator();
  }
  private Comparator<Tuple<PackableString, TimedPackableLong>> keyValueComparator() {
    return new Comparator<Tuple<PackableString, TimedPackableLong>>() {
      @Override
      public int compare(Tuple<PackableString, TimedPackableLong> o1,
                         Tuple<PackableString, TimedPackableLong> o2) {
        int keyCmp =
          String.CASE_INSENSITIVE_ORDER.compare(o1.getKey().get(), o2.getKey().get());
        if (keyCmp == 0) {
          long l1 = o1.getValue().getLong();
          long l2 = o2.getValue().getLong();
          if (l1 < l2)       { return -1; }
          else if (l1 == l2) { return  0; }
          else               { return  1; }
        }
        else {
          return keyCmp;
        }
      }
      @Override
      public boolean equals(Object o) {
        return (this == o);
      }
    };
  }
  private void printKeyValue(Tuple<PackableString, TimedPackableLong> kv) {
    if (time) {
      Date date = new Date(kv.getValue().getTime());
      SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
      System.out.println(df.format(date) +
                         "  accessed: " + kv.getValue().getLong() +
                         "  path: " + kv.getKey().get());
    }
    else {
      System.out.println("accessed: " + kv.getValue().getLong() +
                         "  path: " + kv.getKey().get());
    }
  }
  private void start() throws Exception {
    if (debug) {
      System.out.println("Retrieving log count from: " + gid.toString());
      System.out.println("Target storage node(s): " + storageNodes.size());
    }
    DataGetter<PackableString, TimedPackableLong> dg =
      DataGetter.create(client, PackableString.class, TimedPackableLong.class, gid);
    Iterator<Tuple<PackableString, TimedPackableLong>> it =
      sortedIterator(dg.iterator(), keyValueComparator());
    while (it.hasNext()) {
      Tuple<PackableString, TimedPackableLong> kv = it.next();
      printKeyValue(kv);
    }
    dg.close();                 // don't forget this.
  }
  public static void main(String[] args) throws Exception {
    SssClient client = new SssClient(args);
    LogCountLister app = new LogCountLister(client);
    app.parseArgs(client.getArgs());
    app.start();
    System.exit(0);
  }
}
